TEST DRIVEN DEVELOPMENT para el clásico programa Hola, Mundo

 

EL PROGRAMA HOLAMUNDO

Todo el mundo que se dedique a programar (o a aprender a programar, como es mi caso), tarde o temprano se ha enfrentado a aprender el lenguaje C de Kernighan y Ritchie, y, con motivo de empezar con un programa sencillo como aquel cásico “Hola, Mundo” se ha enfrentado a entender el siguiente código, que en este artículo llamaré, el programa HolaMundo:

 

#include <stdio.h>

 

int main() {

    printf("Hola, mundo\n");

    return 0;

}

 

Mucho tiempo ha pasado hasta que, en la era moderna del .NET, se genera una plantilla de programa C# al ejecutar la sentencia CLI:

c:\...>dotnet new console

con un archivo de código,  Program.cs, que solo contiene un par de líneas:

 

using System;

Console.WriteLine("Hola, mundo");

 

cuando el código equivalente al HolaMundo original debería ser:

 

using System;

class Program {

    static void Main() {

        Console.WriteLine("Hola, mundo");

    }

}

 

Este cambio se produjo en .NET Core 3.1, por allá por 2019, cuando ya estaban más que establecidas las directrices de las pruebas unitarias (MSTest en 2005-2012, Nunit en 2006-2018, xUnit.net en 2008-2018, véase #APENDICE 1 TECNOLOGÍAS Y FECHAS DE LANZAMIENTO)

La simplificación consistió en una sintaxis más concisa para las aplicaciones de consola.

Después de 2019 han seguido evolucionando estas tecnologías, tanto .NET como las propias librerías y marcos de trabajo de las pruebas unitarias, pero lo que no ha cambiado es la plantilla de dos líneas generada por dotnet new console.

 

Yo me alegro cada vez que la simplificación hace evolucionar mi entorno facilitando mi trabajo y haciendo que sea más productivo. De lo que no me alegro es de que la simplificación haga que mi trabajo sea más improductivo, que me haga trabajar más, y, sobre todo, que me haga ignorar que la evolución de la tecnología va por otro camino que no es precisamente paralelo a la simplificación que observo.

Me refiero a que, por un lado, puede ser bienvenida una simplificación que no necesita codificar procedimientos o funciones de manera explícita y, por otro, cada vez más, es exigible la existencia de códigos de prueba que garanticen la calidad del código, de modo que resulta imprescindible la codificación explícita de procedimientos o funciones que puedan ser llamados desde códigos de testeo.

 

Les pondré un ejemplo sencillo de lo que aquí, en este artículo de opinión, argumento: planteo el sencillo problema informático de reescribir el código del HolaMundo en términos de TDD (Test Driven Development), el modelo de desarrollo dirigido o conducido por pruebas.

 

TDD, UN MÉTODO DE DESARROLLO TRIFÁSICO

Primero permítanme darles unas cuantas definiciones, solo para poder argumentar mi opinión con el adecuado nivel divulgativo que deseo.

Parecería lógico, en un principio, que una vez analizado y diseñado un programa informático que resuelve cierto problema de manejo automático de la información de un negocio, se procediera a escribir el código de negocio que resuelve los requisitos preestablecidos en estas primeras etapas de desarrollo de software, y, posteriormente, se proceda a escribir el código de test que, ejecutándose en la etapa de pruebas, valide y garantice la calidad del código de negocio. Todo ello previo a la etapa de puesta en marcha, estableciendo un ciclo de validación de permita probar exhaustivamente la funcionalidad de negocio requerida antes de la implantación.

Sin embargo, dado que estas pruebas son de variada naturaleza, algunas de ellas se pueden automatizar mediante código de test y otras necesitarán probarse mediante intervención humana en esa etapa de pruebas.

El código de test se torna algo fundamental para las pruebas que puedan automatizarse, tanto en esta temprana etapa de pruebas previa a la implantación, como en cualquier momento del ciclo de desarrollo donde, para cualquier cambio en el código, deban volver a garantizarse todas las funcionalidades, tanto las establecidas por los requisitos iniciales como las establecidas en los nuevos requisitos que vayan surgiendo.

La cuestión es si estos códigos de prueba deben escribirse antes o después de los códigos de negocio.

El modelo de desarrollo conducido por pruebas consiste en preparar el código de test que va a probar el código de negocio antes incluso de que se escriba el código de negocio.

Pues bien, mi desafío aquí está en escribir el código de pruebas del programa HolaMundo, antes de escribir el código del propio programa HolaMundo.

¿Qué?

¿Pero qué estás diciendo, José Antonio?

¿Acaso no te das cuenta de que el programa HolaMundo es un programa tan sencillo que no necesita escribirse una rutina que lo testee?

Que basta con compilarlo, ejecutarlo, y cuando veas que  aparece en la consola el letrero de “¡Hola, mundo!” ya sabes que funciona.

 

¿O no? ...

 

Pues no necesariamente. Y en el caso del TDD, la respuesta es un NO rotundo.

 

Analizo a continuación el por qué de esta respuesta rotunda en el caso del modelo de desarrollo dirigido por pruebas:

En el modelo de desarrollo conducido por pruebas tenemos que seguir los siguientes pasos técnicos:

1.      Tenemos que elegir un requisito de software de entre los que estén establecidos para el programa,

2.      Después escribir el código de prueba para el código de negocio que resuelve dicho requisito.

3.      A continuación ejecutar el código de prueba para un caso de uso conocido que daría error de ejecución, comprobando que se produce dicho error.

4.      Seguidamente hacer los cambios oportunos en el código de prueba para un caso de uso conocido que no daría error y que ejecutara con éxito, comprobando que no se produce ningún error.

5.      Finalmente escribir y probar el código de negocio, optimizando dicho código en un ciclo que utiliza el código de prueba cada vez que se realiza alguna optimización, comprobando que la optimización no produce ningún error.

Como se ve aquí, el paso 3 corresponden a una fase del TDD en la cuál el objetivo es provocar un error de ejecución, mientras que el paso 4 corresponde a una fase en la cuál el objetivo es provocar un éxito en la ejecución. Luego el paso 5 pretende optimizar el código de negocio apoyándose en el código de testeo, que ya sabemos por 3 y por 4 que puede dar error y éxito para casos de uso conocidos. Si en el paso 5 se produce un error será necesario volver cíclicamente al paso 5, volver a reescribir y volver a probar, hasta que no se produzca el error.

Por tanto hay 3 fases en TDD, que se han nombrado significativamente:

    La FASE ROJA, la del paso 3, provocando error.

    La FASE VERDE, la del paso 4, provocando éxito.

    La FASE DE REFACTORIZACIÓN, la del ciclo del paso 5, cambiando y optimizando, hasta conseguir éxito.

 

¿Pero, José Antonio, todo ésto cómo lo vas a aplicar al HolaMundo? ¡Que solo tiene una línea de pseudocódigo! ¡Escribir en consola una cadena de caracteres! ¿Qué vas a testear ahí?

 

Voy a ello a continuación:

Paso 1, el requisito: Ya se ha mencionado: ¡Escribir en consola una cadena de caracteres!. Esto tiene dos partes. La primera parte es “escribir en consola”. La segunda parte es “una cadena de caracteres”, pero no cualquier cadena de caracteres, sino exactamente la cadena “¡Hola, mundo!”.

Paso 2, escribir el código de prueba. Lo dejaré para más tarde, porque aquí y ahora no sé qué poner.

Paso 3, la fase roja: El error se produciría si ocurren cualquiera de estas circunstancias:

    error 1: que no se escriba nada en la consola

    error 2: que se escriba algo en la consola pero no sea exactamente la cadena “¡Hola, mundo!” sino alguna subcadena. Caso de escritura parcial.

    error 3: que se escriba en la consola la cadena “!Hola, mundo!” y alguna cadena más. Caso de escritura total, pero acompañada de texto no deseado.

 

Aquí, para simplificar, voy a considerar que se conoce el texto deseado, que puede ser “!Hola, mundo!” o una variación, lo que provoca que los errores 1, 2 y 3 son el mismo, que, resumidamente, se expresan como “en la consola no se ha escrito el texto deseado”.

Paso 4, la fase verde: escribir el código de negocio que cumpla el requisito de escribir en la consola el texto deseado. Por tanto la aserción correspondiente que evaluará la ejecución como exitosa es “en la consola se ha escrito el texto deseado”.

Paso 5, la fase de refactorización: Lo dejaré para más tarde, cuando ya esté escrito el código de negocio no optimizado.

 

CUANDO TDD NO PUEDE SER APLICADO

Digo yo que, para poder escribir un código que testee la ejecución de otro código habrá que saber qué diablos hace el código testeado. En el vocabulario del TDD, argumento que no siempre se podrá escribir el código de prueba del paso 2 antes de conocer el código de negocio del paso 4.

En el ejemplo que nos ocupa no puedo escribir un código que testee si en la consola se ha escrito el texto deseado antes de saber los detalles del código de negocio que escribe en la consola el texto deseado. Y ésto ocurre porque hay varios modos de escribir en la consola el texto deseado.

 

¿Qué estas diciendo, José Antonio?

 

El siguiente código escribe en la consola el texto deseado obteniendo un manejador para el canal “salida estándar” o stdout:

 

using System;

using System.Runtime.InteropServices;

using System.Text;

 

class Program

{

    [DllImport("kernel32.dll", SetLastError = true)]

    static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped);

 

    static void Main()

    {

        string cadena = "Hola, mundo!";

        byte[] bytes = Encoding.Default.GetBytes(cadena);

 

        // Obtener el identificador del flujo de salida estándar (STDOUT)

        IntPtr handle = GetStdHandle(-11);  // -11 corresponde a STD_OUTPUT_HANDLE

 

        // Escribir en la salida estándar utilizando WriteFile

        uint bytesWritten;

        WriteFile(handle, bytes, (uint)bytes.Length, out bytesWritten, IntPtr.Zero);

    }

 

    [DllImport("kernel32.dll", SetLastError = true)]

    static extern IntPtr GetStdHandle(int nStdHandle);

}

 

Este código no es testeable en el sentido sencillo del término testear. Requeriría la escritura de un código test que se ejecutara a la vez que este código de negocio, con la salida estándar redirigida mediante encaminamiento (pipe) a la entrada estándar del código testeador.

Esto es así porque no es posible que el mismo código que escribe un flujo en la salida estándar lea en su entrada estándar ese mismo flujo. O eso creo.

 

La cuestión no es si existe la posibilidad de testear sea cual sea el código de negocio que se desee testear. No, esa no es la cuestión. La cuestión es si, siguiendo la metodología TDD, (donde primero se escribe el código de test antes del código de negocio) es posible escribir el código de test antes de saber detalles de la implementación del código de negocio. Y luego, además, si ese código de test puede ser llamado desde el propio programa que implementa el código de negocio.

 

Yo creo que no. Y si esta duda aparece en un proyecto tan sencillo como el HolaMundo, casi me atrevo a decir que habrá montones de códigos “no pre-testeables”.

 

 

CUANDO LAS PRUEBAS UNITARIAS NO PUEDEN SER APLICADAS CON TRIPLE A (AAA)

Las pruebas unitarias se refieren al procesamiento que se necesita implementar en el código de test para garantizar que se generan errores en los casos de uso que deben generar error. El test o la prueba son “unitarios” porque se necesita probar o testear cada rutina por separado, cada pequeño algoritmo implementado como solución para un requisito o parte de un requisito, por separado del testeo de otro pequeño algoritmo implementado. De este modo, si se usa la técnica del Divide y Vencerás, que descompone los grandes requisitos en sus funcionalidad elementales, y ésto genera múltiples rutinas (procedimientos y funciones) a la hora de escribir el código, se deberá escribir tantas pruebas unitarias como rutinas.

Se habla del paradigma TRIPLE A o AAA de las pruebas unitarias para referirse a las fases que constituyen, en orden, una prueba unitaria, usando las iniciales en inglés de Arrange, Act y Assert.

Así:

La fase Arrange (preparar), es la primera, y se emplea para configurar el entorno y establecer las condiciones iniciales de la prueba.

La fase Act (actuar), es la segunda, y se emplea para realizar la acción invocando la rutina que se está probando o testeando.

La fase Assert(Verificar), es la tercera y última fase de la prueba unitaria, donde se comprueba si los resultados obtenidos coinciden con los resultados esperados.

 

Usualmente en Arrange se instancia el objeto u objetos iniciales, que permiten llegar a la rutina a probar, en Act se llama a dicha rutina pasándole como argumentos los datos correspondientes a un caso de uso conocido, generándose un dato o datos resultado que puede ser directamente utilizado si la rutina es una función o, en caso de ser un método, es obtenible usando algún dato público de los objetos iniciales (como una propiedad o propiedades de dichos objetos, en memoria, en disco, en internet…), y en Assert se comprueba si ese dato o datos resultado es igual a un resultado esperado para dicho caso de uso.

 

Pero, José Antonio, ¿cómo se aplica ésto de Arrange, Act y Assert en esta pequeña rutina que escribe en consola el texto requerido de HolaMundo?

 

A ver, lo que en principio debe estar claro es que la fase de Act es la que ejecuta la acción. Y como en este caso la acción es la de escribir en consola el texto deseado, bastaría con escribir un método que invoque una sentencia

 

Console.WriteLine(“¡Hola, mundo!”);

 

Claro, eso si empezáramos por escribir el código de negocio, pero aquí y ahora, en el paso 2 de la prueba unitaria (fase roja) de TDD aún no sabemos nada del código de negocio.

 

 

Nuestro método para la fase Act solo sería un esqueleto, por ej

 

public static void actuar()

{

      // ejecutar el código de negocio: mostrar en la consola la cadena “¡Hola, mundo!”

      // aquí escribo un comentario con un código

      // que no tiene por qué conocerse ahora en el paso 2 de las pruebas unitarias (fase roja)

      // Console.WriteLine(“¡Hola, mundo!”); // u otro código no conocido

      // pero sigue siendo un comentario

 

}

 

que sería llamado desde el código principal de negocio, un método Main en el programa principal, por ej:

 

public static void Main()

{

      actuar();

}

 

Mientras, desde el código principal de pruebas, otro método main, llamado Main2 para no interferir con la lógica del compilador, también se llama al método actuar():

 

public static void Main2()

{

      // Arrange: preparativos para pruebas unitarias fase roja

      // … aquí falta código

 

      // Act: prueba unitaria fase roja

      actuar();

 

      // Assert: comprobación fase roja, debe producirse un error en tiempo de ejecución

      // … aquí falta código

 

}

 

Si en este estado ejecutamos el programa principal, no va a ocurrir nada, ya que actuar() no contiene      código de negocio.

 

Incluso si cambiamos el código principal a:

 

public static void Main()

{

      // actuar();

      Main2();

}

 

se ejecutaría el programa de pruebas principal, que tampoco hace que ocurra nada.

Se hace necesario que ocurra un error, pero, ¿con qué preparativos y con qué comprobación?

 

Lo primero que se me ocurrió fue que existiera una constante con el valor del texto deseado en el código de negocio:

 

public static void Main()

{

      public static const textodeseado=”!Hola, mundo!”;

      actuar();

}

 

y luego, en el código de pruebas, preparar una copia de la cadena que define el texto deseado. De este modo en la fase Assert se puede comprobar si el valor de la copia coincide con el valor del texto deseado:

 

public static void Main2()

{

      // Arrange: preparativos para pruebas unitarias fase roja

      var copiadeltextodeseado=Main.textodeseado;

 

      // Act: prueba unitaria fase roja

      actuar();

 

      // Assert: comprobación fase roja, debe producirse un error en tiempo de ejecución

      If(!(Main.textodeseado==copiadeltextodeseado)

      {

            throw new Exception("La prueba unitaria falló)”;

      }

}

 

Nota: En el framework de pruebas unitarias MSTest este código Main2 se escribe como

 

[TestMethod]

public static void Main2()

{

      // Arrange: preparativos para pruebas unitarias fase roja

      var copiadeltextodeseado=Main.textodeseado;

...

      // Act: prueba unitaria fase roja

      actuar();

 

      // Assert: comprobación fase roja, debe producirse un error en tiempo de ejecución

      Assert.AreEqual(Main.textodeseado, copiadeltextodeseado);

}

 

donde el método AreEqual del objeto Assert lanza una excepción en tiempo de ejecución cuando el valor de su primer parámetro, el valor esperado, no es igual al valor obtenido al arreglar o llevar a cabo la acción que se desea testear.

 

Pero esto no garantiza que el valor copiado sea lo que finalmente se ha escrito en la consola. No sabemos nada sobre el resultado de la acción, sobre qué demonios haya realizado la llamada a actuar().

 

Luego pensé en una mejora del testeo, sobre todo al observar que lo que se testea bien son las acciones representadas por funciones. En todo caso se testean mejor que los procedimientos.

En efecto las funciones pueden recibir argumentos de prueba, referidos a casos de uso concretos, y devolver valores calculados a partir de esos argumentos, que se pueden confrontar bien con los resultados esperados para tal caso de uso representado por tal o tales argumentos.

En este caso del HolaMundo pensé que el código de actuar() podría recibir un argumento con el valor del texto deseado, y devolver finalmente el argumento empleado en la sentencia que lanzara este texto a la consola.

 

El código de negocio quedaría así:

 

public static void Main()

{

      public static const textodeseado=”!Hola, mundo!”;

      var resultado=actuar(textodeseado);

}

 

public static string actuar(string textodeseado)

{

      // ejecutar el código de negocio: mostrar en la consola la cadena “¡Hola, mundo!”

      // aquí escribo un comentario con un código

      // que no tiene por qué conocerse ahora en el

      // paso 2 de las pruebas unitarias (fase roja)

      // Console.WriteLine(textodeseado); // u otro código no conocido

      // pero sigue siendo un comentario

 

      return textodeseado;

}

 

 

Y el código de prueba quedaría así:

 

 

[TestMethod]

public static void Main2()

{

      // Arrange: preparativos para pruebas unitarias fase roja

      var copiadeltextodeseado=Main.textodeseado;

 

      // Act: prueba unitaria fase roja

      var resultado=actuar(copiadeltextodeseado);

 

      // Assert: comprobación fase roja, debe producirse un error en tiempo de ejecución

      Assert.AreEqual(resultado, copiadeltextodeseado);

}

 

 

Pero, aún así, no me quedaba conforme con que este código de prueba probara nada. De hecho tal y como está escrito, no ocurre ningún error a pesar de que no se muestra el texto deseado por consola.

¿Por qué?

Pues porque en el método actuar() se devuelve el mismo valor de textodeseado que se recibe como argumento, y en el método de prueba Main2, en la fase Assert, no se produce ningún error porque las variables resultado y copiadeltextodeseado valen lo mismo.

Esto prueba que aún no hemos terminado de testear en Main2.

¿Y qué podemos hacer, José Antonio, para saber si la llamada a actuar() ha escrito algo en la consola?

Pues, en mi opinión, hay que codificar más cosas en el código de los preparativos de la fase de Arrange: hay que desviar el flujo de la consola para que lo que se escriba en ella vaya a parar a un flujo que conozcamos en Main2 y que controlemos desde Main2.

Esto se puede hacer fácilmente con el siguiente código de negocio:

 

public static void Main()

{

      public static const textodeseado=”!Hola, mundo!”;

      actuar(textodeseado);

}

 

public static void actuar(string textodeseado)

{

      // ejecutar el código de negocio:

      // mostrar en la consola la cadena “¡Hola, mundo!”,

      // recibida en el argumento asociado al parámetro textodeseado

      Console.WriteLine(textodeseado);

      // no devuelve nada, ya que no se necesita para testear

}

 

 

y con el siguiente código de pruebas:

 

[TestMethod]

public static void Main2()

{

      // Arrange: preparativos para pruebas unitarias

      var copiadeltextodeseado=Main.textodeseado;

 

      //copia del flujo usual de la consola

      var copia_stdout=Console.Out;

      //creación de un nuevo flujo

      var nuevostdout=new StringWriter();

      //redirección de la consola al nuevo flujo

      Console.SetOut(nuevostdout);

 

      // Act: prueba unitaria

      actuar(Main.textodeseado);

 

      // Assert: comprobación fase roja, debe producirse un error en tiempo de ejecución

      var resultado=nuevostdout.ToString().Trim();

      Assert.AreEqual(resultado, copiadeltextodeseado);

 

      //Arrange-End: tareas para deshacer los cambios en el sistema provocados por los preparativos de la fase de Arrange

     

      //redirección de la consola al flujo usual a partir de la copia

      Console.SetOut(copia_stdout);

}

 

Esta solución, que es mi propuesta, tiene las siguientes implicaciones:

1.- Finalmente he escrito la función actuar() completamente.

Si se comenta la línea

      //Console.WriteLine(textodeseado);

se produce el error esperado en el punto 2 de la prueba unitaria (fase roja), porque la variable resultado contiene una cadena vacía.

Si se deja sin comentar

      Console.WriteLine(textodeseado);

no se produce ningún error, lo que es de esperar en el punto 3 de la prueba unitaria (fase verde).

2.- El método actuar() ya no necesita devolver ningún valor para proceder a su validación, ya que la prueba se obtiene directamente del objeto Console, cuyo flujo, Console.Out, está redirigido al ejecutar la prueba unitaria a través del método Main2(), lo que ocurre sólo en tiempo de ejecución cuando el programa esté siendo testeado por pruebas unitarias.

3.- El flujo del objeto Console estará encaminado correctamente a la salida estándar convencional si el método actuar() se ejecuta desde Main(), lo que ocurrirá en tiempo de ejecución cuando el programa esté siendo usado “en producción”.

4.- Puede que exista alguna alternativa o mejora de este código que permita escribir la sección de Arrange sin conocer nada de la sección de Act, pero yo no consigo imaginarlo. Mientras no reciba muestras de que estoy equivocado, por tanto, para mi entender, habrá muchas ocasiones en las que es imposible escribir un test AAA ordenado, codificando sucesivamente cada sección A.

5.- De hecho, si se sustituye el código de actuar() por el que he expuesto anteriormente basado en usar la librería del sistema, kernel32.dll, el assert siempre dará error.

También dará error si, dentro del propio código de actuar(), se habilita y se desvia la consola ya trucada hacia otro nuevo segundo flujo de salida estándar, y luego se restaura antes de salir del propio código de actuar(). Concluyo sin que me quede una pizca de duda que habrá muchos casos donde será imposible enfrentarse a la escritura del código de pruebas sin conocer antes la naturaleza  del código de negocio que se desea probar. No digo que tenga que estar ya escrito, pero sí tendrá que estar ya analizado y diseñado.

TDD no es la metodología palíndroma de la CDD (Code Driven Development): En muchos casos habrá que dedicar algo de tiempo al código de negocio antes de abordar seriamente el código de pruebas.

6.- En aquellas situaciones en las que se altere de alguna manera el sistema informático durante la fase de Arrange será necesario, como en este caso, un segundo Arrange al final para deshacer los cambios y que el sistema quede como estaba antes de enfrentarse a la prueba unitaria. Por tanto la sigla no son 3 aes, sino 4, AAAA. Léase aquí que la primera A es un Arrange1, un Arrange-Begin, y la cuarta A es un Arrange2, un Arrange-End, un conjunto de preparativos para dejar el sistema en el mismo estado que se empezó a usar: redireccione los flujos a su estado inicial (como en este caso), libere memoria de objetos que ya no se necesiten, borre archivos temporales, ...

libere recursos, …

 

7.- Como paso previo a la redacción de este informe escribí el código en Visual Studio Code que pueden ver en #APÉNDICE 2 EL CÓDIGO DE NEGOCIO, y en #APÉNDICE 3 EL CÓDIGO DE PRUEBAS. En este código, que a diferencia del insertado en este artículo ha sido compilado y ejecutado, además de lo expuesto, observen que propongo solo lanzar el código de negocio cuando la ejecución del código de prueba haya sido superada.

 

Esto lo hago con el código de negocio:

public static void Main()

{

      public static const textodeseado=”!Hola, mundo!”;

     

      if(Main2(textodeseado))

      {

            actuar(textodeseado);

      }

}

 

public static void actuar(string textodeseado)

{

      // ejecutar el código de negocio:

      // mostrar en la consola la cadena recibida como parámetro

      Console.WriteLine(textodeseado);

}

 

 

y con el siguiente código de pruebas:

[TestMethod]

public static bool Main2()

{

      // Arrange: preparativos para pruebas unitarias

      var copiadeltextodeseado=Main.textodeseado;

 

      //copia del flujo usual de la consola

      var copia_stdout=Console.Out;

      //creación de un nuevo flujo

      var nuevostdout=new StringWriter();

      //redirección de la consola al nuevo flujo

      Console.SetOut(nuevostdout);

 

      // Act: prueba unitaria

      actuar(Main.textodeseado);

 

      // Assert: comprobación fase roja, debe producirse un error en tiempo de ejecución

      var resultado=nuevostdout.ToString().Trim();

      Assert.AreEqual(resultado, copiadeltextodeseado);

 

      //Arrange-End: tareas para deshacer los cambios en el sistema provocados por los preparativos de la fase de Arrange

     

      //redirección de la consola al flujo usual a partir de la copia

      Console.SetOut(copia_stdout);

 

      return true;

}

 

Esta propuesta será tanto más útil para aquellos casos en los que la ejecución de código consume recursos de pago, donde, en fase de pruebas, pueda ser convenientes realizar una pasada por el código de prueba, antes de lanzar el código de negocio.

 

 

Por José Antonio Rojas Delgado, 23/1/2024

Más artículos aquí

 

 

 

 


APÉNDICE 1: Véase aquí la tabla que me ha construido ChatGPT 3.5:

Tecnologías y Fechas de Lanzamiento

Tecnología

Versión o Lanzamiento

Fecha de Lanzamiento

Año de Lanzamiento

MSTest (Visual Studio Unit Testing Framework)

2005 (VS 2005)

Noviembre de 2005

2005

NUnit

2.0

Abril de 2006

2006

xUnit.net

1.0

Septiembre de 2008

2008

MSTest (Visual Studio Unit Testing Framework)

2010 (VS 2010)

Abril de 2010

2010

MSTest (Visual Studio Unit Testing Framework)

2012 (VS 2012)

Septiembre de 2012

2012

xUnit.net

2.0

Febrero de 2015

2015

NUnit

3.0

Diciembre de 2015

2015

.NET Core

1.0

Junio de 2016

2016

.NET Core

2.0

Agosto de 2017

2017

NUnit

3.11

Agosto de 2018

2018

xUnit.net

2.4

Julio de 2018

2018

.NET Core

3.0

Septiembre de 2019

2019

.NET Core

3.1

Diciembre de 2019

2019

NUnit

3.12

Abril de 2020

2020

xUnit.net

2.4.1

Diciembre de 2020

2020

.NET 5 (Reemplaza a .NET Core)

5.0

Noviembre de 2020

2020

 

 

 


APÉNDICE 2: El código de negocio, código final para el programa principal. Una modificación a mi gusto y más didáctica del que me propuso ChatGPT 3.5.

 

 




 

 

 

 

APÉNDICE 3: El código de test, código final para el programa de pruebas, basado en MSTest